﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;

namespace Agent.Messaging
{
  /// <summary>
  /// Object that can be used to implement an explicit state machine and help debug issues with
  /// invalid state transitions, etc.
  /// </summary>
  /// <remarks>
  /// The only method on this class that is explicitly thread-safe is <see cref="GoToState"/> which
  /// ensures only one thread can change state at a time. Any other synchronisation must be done
  /// by the caller
  /// </remarks>
  /// <typeparam name="T">The enum used to represent valid states</typeparam>
  public class StateMachineHelper<T> where T : struct, IComparable
  {
    /// <summary>
    /// The current state of the state machine
    /// </summary>
    /// <remarks>
    /// The ONLY place this should ever be written to is in the GoToState method
    /// </remarks>
    T currentState;

    /// <summary>
    /// A dictionary of all the valid "normal" transitions from [current state] -> [ list of valid states ]
    /// </summary>
    IDictionary<T, IEnumerable<T>> allowedTransitions;

    /// <summary>
    /// List of special "sink" states - such as an Error state - that can always be 
    /// transitioned to
    /// </summary>
    IEnumerable<T> sinkStates;

    /// <summary>
    /// Whether or not transitions from any state to itself are allowed
    /// </summary>
    /// <remarks>
    /// If only some states can transition to themselves, this should be false and the explicit transitions
    /// should be set in the allowedTransitions dictionary
    /// </remarks>
    bool allowTransitionToCurrentState;

    /// <summary>
    /// Creates a new state machine helper
    /// </summary>
    /// <param name="allowedTransitions">The set of allowed transitions, of the form [state]->[set of new states]</param>
    /// <param name="sinkStates">A set of 'sink' states that can always be transitioned to, such as an error state</param>
    /// <param name="initialState">The initial state of the state machine</param>
    /// <param name="allowTransitionToCurrentState">Whether or not the state machine can transition to the same state</param>
    public StateMachineHelper(IDictionary<T, IEnumerable<T>> allowedTransitions, IEnumerable<T> sinkStates, T initialState, bool allowTransitionToCurrentState)
    {
      if (allowedTransitions == null || allowedTransitions.Count == 0)
        throw new ArgumentException("Must have at least one transition defined", "allowedTransitions");

      if (allowedTransitions.ContainsKey(initialState) != true)
        throw new ArgumentException("Initial state must exist as a start state in the allowedTransitions dictionary", "initialState");

      this.currentState = initialState;
      this.allowedTransitions = allowedTransitions;
      this.sinkStates = sinkStates ?? new T[] { };
      this.allowTransitionToCurrentState = allowTransitionToCurrentState;
    }

    /// <summary>
    /// Returns the current state of the state machine
    /// </summary>
    public T CurrentState { get { lock (this) { return currentState; } } }

    /// <summary>
    /// Whether or not transitions from a state to itself are always allowed
    /// </summary>
    public bool AllowTransitionToCurrentState { get { return allowTransitionToCurrentState; } }

    /// <summary>
    /// Checks whether the state machine is currently in the specified state
    /// </summary>
    /// <param name="state">The state to check for</param>
    /// <returns>true if in the specified state; false otherwise</returns>
    public bool IsInState(T state)
    {
      return CurrentState.Equals(state);
    }

    /// <summary>
    /// Checks whether the state machine is currently in one of the specified states
    /// </summary>
    /// <param name="possibleStates">One or more states to check against</param>
    /// <returns>true if in one of the specified states; false otherwise</returns>
    public bool IsInState(IEnumerable<T> possibleStates)
    {
      return possibleStates.Contains(CurrentState);
    }

    /// <summary>
    /// Asserts that the state machine is in the specified state
    /// </summary>
    /// <param name="state">The state the machine is supposed to be in</param>
    /// <remarks>Only used in DEBUG builds</remarks>
    [Conditional("DEBUG")]
    public void Assert(T state)
    {
      if (IsInState(state) != true)
        Debug.Assert(false, "Current state " + CurrentState + " is not valid!");
    }

    /// <summary>
    /// Asserts that the state machine is in one of the specified states
    /// </summary>
    /// <param name="possibleStates">One or more states the state machine is supposed to be in</param>
    /// <remarks>Only used in DEBUG builds</remarks>
    [Conditional("DEBUG")]
    public void Assert(IEnumerable<T> possibleStates)
    {
      if (IsInState(possibleStates) != true)
        Debug.Assert(false, "Current state " + CurrentState + " is not valid!");
    }

    /// <summary>
    /// Checks to see if a transition to the specified state is allowed
    /// </summary>
    /// <param name="newState">The new state to check for</param>
    /// <returns>true if the transition is allowed; false otherwise</returns>
    public bool CanGoToState(T newState)
    {
      Exception dummy;

      // Don't care about the exception
      return CanGoToState(newState, out dummy);
    }

    /// <summary>
    /// Checks to see if a transition to the specified state is allowed
    /// </summary>
    /// <param name="newState">The new state to check for</param>
    /// <param name="exception">An exception object that can be thrown if the return value is false</param>
    /// <returns>true if the transition is allowed; false otherwise</returns>
    internal bool CanGoToState(T newState, out Exception exception)
    {
      exception = null;
      if (newState.Equals(currentState) && AllowTransitionToCurrentState)
        return true;

      if (sinkStates.Contains(newState))
        return true;

      if (allowedTransitions.ContainsKey(newState) == false)
      {
        exception = new ArgumentOutOfRangeException(newState + " is not a valid state", "newState");
        return false;
      }

      var allowedStates = allowedTransitions[currentState];

      if (allowedStates.Contains(newState))
        return true;

      exception = new InvalidOperationException("Can't switch from " + currentState + " to " + newState);
      return false;
    }

    /// <summary>
    /// Transitions the state machine to the new state
    /// </summary>
    /// <param name="newState">The new state to transition to</param>
    /// <remarks>This method is thread safe; only one thread can change states at a time</remarks>
    /// <exception cref="System.ArgumentOutOfRangeException">Thrown if the state isn't a valid state</exception>
    /// <exception cref="System.InvalidOperationException">Thrown if the state transition is not valid</exception>
    public void GoToState(T newState)
    {
      Exception ex = null;
      lock (this)
      {
        if (CanGoToState(newState, out ex) != true)
          throw ex;

        currentState = newState;
      }
    }
  }
}
